# (C) Copyright Peter Hinch 2017-2019.
# Released under the MIT licence.

from mqtt_as import MQTTClient
from mqtt_local import config
import uasyncio as asyncio
import ubinascii
import machine
import time
import random
import json
# 从配置文件导入引脚定义和产品信息
from mqtt_local import LAMP_PIN, AC_PIN, ALARM_PIN, FAN_PIN, PRODUCT_ID, USER_ID

# 自动获取ESP32的MAC地址（大写格式）
def get_mac_address():
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    mac = ubinascii.hexlify(wlan.config('mac'), ':').decode().upper()
    wlan.active(False)  # 关闭WiFi以节省功耗
    return mac

# 获取MAC地址（大写格式）
MAC_ADDRESS = get_mac_address()
print(f"设备MAC地址: {MAC_ADDRESS}")

# 构建Client ID: "S" + "&" + MAC + "&" + product_id + "&" + user_id
CLIENT_ID = f"S&{MAC_ADDRESS}&{PRODUCT_ID}&{USER_ID}"
print(f"生成的Client ID: {CLIENT_ID}")

# 构建订阅/发布主题
SUBSCRIBE_FUNCTION_TOPIC = f"$sys/{PRODUCT_ID}/{MAC_ADDRESS}/silizap/function/get"
SUBSCRIBE_OTA_TOPIC = f"$sys/{PRODUCT_ID}/{MAC_ADDRESS}/silizap/ota/get"
SUBSCRIBE_PROPERTY_TOPIC = f"$sys/{PRODUCT_ID}/{MAC_ADDRESS}/silizap/monitor/get"
PUBLISH_TOPIC = f"$sys/{PRODUCT_ID}/{MAC_ADDRESS}/silizap/property/post"
PUBLISH_ACK_TOPIC = f"$sys/{PRODUCT_ID}/{MAC_ADDRESS}/silizap/function/get_reply"
PUBLISH_COMBINE_ACK_TOPIC = f"$sys/{PRODUCT_ID}/{MAC_ADDRESS}/silizap/monitor/get_reply"

def generate_did():
    """生成随机的did值"""
    # 生成32个随机字符的十六进制字符串
    return ''.join('{:02x}'.format(random.randint(0, 255)) for _ in range(16))

def read_sht31():
    """从SHT31传感器读取温湿度数据"""
    try:
        # 发送测量命令
        i2c.writeto(SHT31_ADDR, bytes([0x2C, 0x06]))
        time.sleep_ms(50)
        
        # 读取6字节数据
        data = i2c.readfrom(SHT31_ADDR, 6)
        
        # 计算温度和湿度
        temp_raw = (data[0] << 8) | data[1]
        hum_raw = (data[3] << 8) | data[4]
        
        temperature = -45 + (175 * temp_raw / 65535.0)
        humidity = 100 * hum_raw / 65535.0
        
        return round(temperature, 2), round(humidity, 2)
    except Exception as e:
        print(f"读取SHT31传感器失败: {e}")
        # 出错时返回默认值
        return 0.0, 0.0

def get_device_status():
    """获取四个外设的开关状态"""
   
    try:
        # 读取引脚状态，高电平表示打开，低电平表示关闭
        lamp_state = "open" if LAMP_PIN.value() == 1 else "close"
        ac_state = "open" if AC_PIN.value() == 1 else "close"
        alarm_state = "open" if ALARM_PIN.value() == 1 else "close"
        fan_state = "open" if FAN_PIN.value() == 1 else "close"
        
        return lamp_state, ac_state, alarm_state, fan_state
    except Exception as e:
        print(f"读取设备状态失败: {e}")
        # 出错时返回默认关闭状态
        return "close", "close", "close", "close"

def control_device(device_name, command):
    """控制指定设备的开关状态"""
    try:
        # 确定要控制的引脚
        pin = None
        if device_name == "lamp":
            pin = LAMP_PIN
        elif device_name == "ac":
            pin = AC_PIN
        elif device_name == "alarm":
            pin = ALARM_PIN
        elif device_name == "fan":
            pin = FAN_PIN
        else:
            print(f"未知设备: {device_name}")
            return False
        
        # 执行控制命令
        if command == "open":
            pin.value(1)  # 高电平开启
            print(f"设备 {device_name} 已开启")
        elif command == "close":
            pin.value(0)  # 低电平关闭
            print(f"设备 {device_name} 已关闭")
        else:
            print(f"未知命令: {command}")
            return False
        
        return True
    except Exception as e:
        print(f"控制设备失败: {e}")
        return False

async def publish_device_data(client, custom_did=None):
    """发布设备数据到MQTT服务器"""
    # 生成随机did或使用传入的did
    did = custom_did if custom_did else generate_did()
    
    # 获取当前时间
    current_time = time.localtime()
    time_str = f"{current_time[0]:04d}-{current_time[1]:02d}-{current_time[2]:02d} {current_time[3]:02d}:{current_time[4]:02d}:{current_time[5]:02d}"
    timestamp = int(time.time() * 1000)  # 毫秒时间戳
    
    # 获取外设状态
    lamp_state, ac_state, alarm_state, fan_state = get_device_status()
    
    # 获取温湿度数据
    # temperature, humidity = read_sht31()
    temperature, humidity = 0.0, 0.0
    
    # 构造JSON数据
    data = {
        "did": did,
        "version": "1.0",
        "params": {
           "time": time_str,
            "timestamp": timestamp,
            "functions": [
                {"id": "lamp", "value": lamp_state, "remark": ""},
                {"id": "ac", "value": ac_state, "remark": ""},
                {"id": "alarm", "value": alarm_state, "remark": ""},
                {"id": "fan", "value": fan_state, "remark": ""}
            ],
            "properties": [
                {"id": "temperature", "value": f"{temperature:.2f}", "remark": ""},
                {"id": "humidity", "value": f"{humidity:.2f}", "remark": ""}
            ]
        }
    }
    
    # 转换为JSON字符串
    publish_msg = json.dumps(data)
    print(f'发布消息到 {PUBLISH_TOPIC}: {publish_msg}')
    
    # 发布消息（若WiFi断开，会自动等待重连）
    await client.publish(PUBLISH_TOPIC, publish_msg, qos=1)

async def publish_ack_response(client, did, params, is_combine=False):
    """发布ACK回复消息到平台"""
    # 获取当前时间
    current_time = time.localtime()
    time_str = f"{current_time[0]:04d}-{current_time[1]:02d}-{current_time[2]:02d} {current_time[3]:02d}:{current_time[4]:02d}:{current_time[5]:02d}"
    timestamp = int(time.time() * 1000)  # 毫秒时间戳
    
    # 构造ACK回复数据
    ack_data = {
        "code": 200,
        "did": did,
        "msg": "",
        "version": "1.0",
        "params": {
            "functions": params,
            "time": time_str,
            "timestamp": timestamp
        },
        "product_id": PRODUCT_ID,
        "serial_number": MAC_ADDRESS,
    }
    
    # 转换为JSON字符串
    ack_msg = json.dumps(ack_data)
    
    # 根据是否为一键控制选择发布主题
    if is_combine:
        print(f'发布一键控制ACK回复到 {PUBLISH_COMBINE_ACK_TOPIC}: {ack_msg}')
        await client.publish(PUBLISH_COMBINE_ACK_TOPIC, ack_msg, qos=1)
    else:
        print(f'发布ACK回复到 {PUBLISH_ACK_TOPIC}: {ack_msg}')
        await client.publish(PUBLISH_ACK_TOPIC, ack_msg, qos=1)

async def execute_command_sequence(client, commands):
   
    """执行一键控制的指令序列"""
    # TUDO
   
    # 所有指令执行完成后，上报设备状态
    await publish_device_data(client)

def sub_cb(topic, msg, retained):
    """收到订阅主题消息的回调函数"""
    topic_str = topic.decode()
    msg_str = msg.decode()
    print(f"收到消息 - 主题: {topic_str}, 内容: {msg_str}, 保留标志: {retained}")
    
    # 处理功能控制命令
    if topic_str == SUBSCRIBE_FUNCTION_TOPIC:
        try:
            # 解析JSON消息
            data = json.loads(msg_str)
            
            # 检查消息格式是否正确
            if "params" in data and isinstance(data["params"], list):
                for param in data["params"]:
                    if "sensorname" in param and "sensorcmd" in param:
                        device_name = param["sensorname"]
                        command = param["sensorcmd"]
                        
                        # 特殊处理status指令
                        if device_name == "status" and command == "open":
                            print("收到status指令，立即刷新数据上报")
                            # 获取消息中的did（如果存在）
                            msg_did = data.get("did", None)
                            # 创建一个任务来发布数据（不阻塞当前回调）
                            loop = asyncio.get_event_loop()
                            loop.create_task(publish_device_data(client, msg_did))
                        else:
                            # 控制设备
                            success = control_device(device_name, command)
                            
                            if success:
                                print(f"成功处理设备 {device_name} 的 {command} 命令")
                                # 控制成功后立即上报设备状态
                                msg_did = data.get("did", None)
                                loop = asyncio.get_event_loop()

                                 # 发送ACK回复到平台
                                loop.create_task(publish_ack_response(client, msg_did, data["params"]))
                                 
                                loop.create_task(publish_device_data(client, msg_did))
                                
                            else:
                                print(f"处理设备 {device_name} 的 {command} 命令失败")
        except json.JSONDecodeError:
            print("JSON解析失败")
        except Exception as e:
            print(f"处理消息时出错: {e}")
    
    # 处理property主题的消息（一键控制）
    elif topic_str == SUBSCRIBE_PROPERTY_TOPIC:
        try:
            # 解析JSON消息
            data = json.loads(msg_str)
            
            # 检查消息格式是否正确
            if "params" in data and isinstance(data["params"], list):
                for param in data["params"]:
                    # 检查是否为一键控制指令
                    if "sensorname" in param and param["sensorname"] == "combinecontroll":
                        print("收到一键控制指令")
                        
                        # 获取消息中的did
                        msg_did = data.get("did", None)
                        
                        # 提取指令序列
                        commands = []
                        if "extdata" in param and "data" in param["extdata"]:
                            commands = param["extdata"]["data"]
                            
                        if commands:
                            print(f"开始执行一键控制指令序列，共{len(commands)}条指令")
                            
                            # 创建异步任务执行指令序列
                            loop = asyncio.get_event_loop()
                            loop.create_task(execute_command_sequence(client, commands))
                            
                            # 立即发送ACK回复，表示已收到并开始执行一键控制指令
                            loop.create_task(publish_ack_response(client, msg_did, data["params"], is_combine=True))
                        else:
                            print("一键控制指令序列为空")
        except json.JSONDecodeError:
            print("JSON解析失败")
        except Exception as e:
            print(f"处理一键控制消息时出错: {e}")

async def wifi_han(state):
    """WiFi连接状态回调"""
    print(f'WiFi状态: {"已连接" if state else "已断开"}')
    await asyncio.sleep(1)

async def conn_han(client):
    """连接成功后的回调（订阅主题）"""
    await client.subscribe(SUBSCRIBE_FUNCTION_TOPIC, qos=1)  # 订阅指定主题，QoS=1确保可靠传输
    print(f"已成功订阅主题: {SUBSCRIBE_FUNCTION_TOPIC}")
    await client.subscribe(SUBSCRIBE_OTA_TOPIC, qos=1)  # 订阅指定主题，QoS=1确保可靠传输
    print(f"已成功订阅主题: {SUBSCRIBE_OTA_TOPIC}")
    await client.subscribe(SUBSCRIBE_PROPERTY_TOPIC, qos=1)  # 订阅指定主题，QoS=1确保可靠传输
    print(f"已成功订阅主题: {SUBSCRIBE_PROPERTY_TOPIC}")
async def main(client):
    """主函数：连接服务器并循环发布消息"""
    await client.connect()
    print("MQTT服务器连接成功")
    
    # 等待2秒，确保订阅生效
    await asyncio.sleep(2)
    
    while True:
        # 使用新的publish_device_data函数发布数据
        await publish_device_data(client)
        
        await asyncio.sleep(10)  # 每10秒发布一次

# MQTT配置参数（整合设备认证信息）
config['subs_cb'] = sub_cb          # 消息回调函数
config['connect_coro'] = conn_han   # 连接成功回调
config['wifi_coro'] = wifi_han      # WiFi状态回调

config["client_id"] = CLIENT_ID  # 使用自动生成的Client ID

# 调试模式（生产环境可设为False）
MQTTClient.DEBUG = True

# 初始化客户端并运行
client = MQTTClient(config)
try:
    asyncio.run(main(client))
finally:
    client.close()  # 确保断开连接
    asyncio.new_event_loop()  # 重置事件循环